<?php
/**
 * Dependency parser php adapter
 *
 * PHP version 5
 *
 * @category  W
 * @package   Adapters
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id:$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
require_once 'W/W.php';
require_once 'W/Dependency/Parser/Adapter/Interface.php';
/**
 * Dependency parser php adapter
 *
 * @category  W
 * @package   Adapters
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
class W_Dependency_Parser_Adapter_Php implements W_Dependency_Parser_Adapter_Interface
{
    public function isExistingItem($item)
    {
        return W_W::fs()->isExistingFile($item);
    }
    public function readItem($item)
    {
        return W_W::fs()->read($item);
    }
    public function tokenizeItemContent($itemContent)
    {
        return token_get_all($itemContent);
    }
    public function parseTokenizedItem($tokens,$realPath,$info=null)
    {
        $events = array();

        $project = 'default';
        if (true === isset($info['project'])) {
            $project = $info['project'];
        }

        $root = null;
        if (true === isset($info['root'])) {
            $root = $info['root'];
        }

        $tokenCount = count($tokens);

        $in      = array();
        $current = array();

        $in['functionLine'] = false;
        $in['interface']    = false;
        $in['class']        = false;

        $current['root']        = str_replace('\\','/',$root);
        $current['class']       = null;
        $current['parentClass'] = null;

        $realPath = str_replace('\\','/',$realPath);

        for ($i = 0; $i < $tokenCount; $i ++) {
            $token = $tokens[$i];
            if (false === is_array($token)) {
                $token = array($token , $token);
            }
            $dependency = null;
            switch ($token[0]) {
                case ';': $in['functionLine'] = false; break;
                case '{': $in['functionLine'] = false; break;
		case T_INTERFACE:
                case T_CLASS:
                    $in['class'] = true;
                    list ($current['class'], $i) = $this->parseClassNameLine($tokens, $i + 1);
                    $dependency = $this->formatDependency($current['class'], $realPath, $current);
                    if ($dependency) {
                        $events[] = array('eventType'=>'class', 'eventData'=>array('dependency' => $dependency , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_INTERFACE: $in['interface'] = true; break;
                case T_REQUIRE:
                case T_REQUIRE_ONCE:
                case T_INCLUDE:
                case T_INCLUDE_ONCE:
                    list ($dependency, $i) = $this->parseDependencyLine($tokens, $i + 1);
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        $events[] = array('eventType'=>$token[1], 'eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_EXTENDS:
                    list ($dependency, $i) = $this->parseExtendsOrImplementsLine($tokens, $i + 1);
                    $in['class'] = true;
                    $current['parentClass'] = $dependency;
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        $events[] = array('eventType'=>'extends', 'eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_IMPLEMENTS:
                    list ($dependency, $i) = $this->parseExtendsOrImplementsLine($tokens, $i + 1);
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        $events[] = array('eventType'=>'implements','eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_NEW:
                    list ($dependency, $i) = $this->parseNewLine($tokens, $i + 1);
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        if ('self' === $dependency) {
                            $dependency = $current['class'];
                        }
                        $events[] = array('eventType'=>'new', 'eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_DOUBLE_COLON:
                    list ($dependency, $i) = $this->parseStaticLine($tokens, $i);
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        if ('self' === $dependency) {
                            $dependency = $current['class'];
                        }
                        $events[] = array('eventType'=>'static', 'eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_VARIABLE:
                    if (false === $in['functionLine']) {
                        break;
                    }
                    list ($dependency, $i) = $this->parseTypeLine($tokens, $i, $in['interface']);
                    $dependency = $this->formatDependency($dependency, $realPath, $current);
                    if ($dependency) {
                        $events[] = array('eventType'=>'type', 'eventData'=>array('dependency' => $dependency , 'class' => $current['class'] , 'file' => $realPath, 'project'=>$project,'root'=>$current['root']));
                    }
                    break;
                case T_FUNCTION: $in['functionLine'] = true; break;
            }
        }
        return $events;
    }
    public function formatDependency ($dependency, $file, $current)
    {
        $dependency = trim($dependency);
        $dependency = str_replace('\\','/',$dependency);
        if (0 < preg_match('|^dirname\s*\(\s*__FILE__\s*\)\s*\.\s*[\'"]\s*[a-zA-Z0-9_/\.]+\s*[\'"]$|',$dependency)) {
            $dependency = preg_replace('|^dirname\s*\(\s*__FILE__\s*\)\s*|',"'".str_replace('\\','/',dirname($file))."'",$dependency);
            eval('$dependency = '.str_replace('\\','/',$dependency).';');
            $dependency = trim($dependency);
        }
        $dependency = preg_replace('|dirname\s*\(\s*__FILE__\s*\)|',"'".str_replace('\\','/',dirname($file))."'",$dependency);
        $dependency = trim($dependency);
        $dependency = preg_replace('|^realpath\s*\(\s*(.+)\s*\)$|','\\1',$dependency);
        $dependency = trim($dependency);
        $dependency = str_replace('__FILE__', "'" . str_replace('\\','/',realpath($file)) . "'", $dependency);
        if (true === isset($current['class'])) {
            $dependency = str_replace('__CLASS__', "'" . $current['class'] . "'", $dependency);
        }
        if (true === isset($current['parentClass'])) {
            $dependency = str_replace('parent', "'" . $current['parentClass'] . "'", $dependency);
        }
        if (0 === strlen($dependency)) {
            return null;
        }
        if ('(' === $dependency{0}) {
            $dependency = substr($dependency,1);
        }
        $lastChar = strlen($dependency)-1;
        if (')' === $dependency{$lastChar}) {
            $dependency = substr($dependency,0,strlen($dependency)-1);
        }
//        if (0 < preg_match('|^[a-zA-Z0-9\./\\\_]+$|',$dependency)) {
//            return $dependency;
//        }
        $matches = null;
        if (0 < preg_match('|^[\'"]\s*([a-zA-Z0-9\:\./\-_]+)\s*[\'"]$|',$dependency,$matches)) {
            $dependency = trim($matches[1]);
        }
        if (0 < preg_match('|^[\'"][^\$\'"]+[\'"](\s*\.\s*[\'"][^\$\'"]+[\'"])*$|',$dependency)) {
            eval('$dependency = '.$dependency.';');
        }
        if (0 < strlen($dependency)) {
            $realPath = @realpath($dependency);
            if ('' !== $realPath && @file_exists($realPath)) {
                $dependency = str_replace('\\','/',$realPath);
            }
        }

        if (0 === strlen($dependency)) {
            return null;
        }
        if (true === isset($current['root'])) {
            $root = str_replace('\\','/',$current['root']);
            $oldDep = $dependency;
            if ($root.'/' === substr($dependency,0,strlen($root)+1)) {
                $dependency = substr($dependency,strlen($root)+1);
            }elseif ($root === substr($dependency,0,strlen($root))) {
                $dependency = substr($dependency,strlen($root));
            }
        }
        if (0 === strlen($dependency)) {
            return null;
        }

        return $dependency;
    }
    protected function parseClassNameLine ($tokens, $nextId)
    {
        $token = $tokens[$nextId];
        $tokenValue = $token;
        if (is_array($token)) {
            $t = $token[0];
            $tokenValue = $token[1];
            $token = $t;
        }
        $tokenCount = count($tokens);
        while (null !== $token && $nextId < $tokenCount && T_STRING !== $token) {
            $token = $tokens[++ $nextId];
            $tokenValue = $token;
            if (is_array($token)) {
                $t = $token[0];
                $tokenValue = $token[1];
                $token = $t;
            }
        }
        if (T_STRING === $token) {
            return array($tokenValue , $nextId);
        }
        return array(null , $nextId);
    }
    protected function parseExtendsOrImplementsLine ($tokens, $nextId)
    {
        $token = $tokens[$nextId];
        $tokenValue = $token;
        if (is_array($token)) {
            $t = $token[0];
            $tokenValue = $token[1];
            $token = $t;
        }
        $tokenCount = count($tokens);
        while (null !== $token && $nextId < $tokenCount && T_STRING !== $token) {
            $token = $tokens[++ $nextId];
            $tokenValue = $token;
            if (is_array($token)) {
                $t = $token[0];
                $tokenValue = $token[1];
                $token = $t;
            }
        }
        if (T_STRING === $token) {
            return array($tokenValue , $nextId);
        }
        return array(null , $nextId);
    }
    protected function parseNewLine ($tokens, $nextId)
    {
        $token = $tokens[$nextId];
        $tokenValue = $token;
        if (is_array($token)) {
            $t = $token[0];
            $tokenValue = $token[1];
            $token = $t;
        }
        $tokenCount = count($tokens);
        while (null !== $token && $nextId < $tokenCount && T_STRING !== $token && T_CONSTANT_ENCAPSED_STRING !== $token && T_VARIABLE !== $token) {
            $token = $tokens[++ $nextId];
            $tokenValue = $token;
            if (is_array($token)) {
                $t = $token[0];
                $tokenValue = $token[1];
                $token = $t;
            }
        }
        switch ($token) {
            case T_STRING:
            case T_CONSTANT_ENCAPSED_STRING:
                return array($tokenValue , $nextId);
        }
        return array(null , $nextId);
    }
    protected function parseStaticLine ($tokens, $nextId)
    {
        $oldNextId = $nextId;
        $token = $tokens[$nextId];
        $tokenValue = $token;
        if (is_array($token)) {
            $t = $token[0];
            $tokenValue = $token[1];
            $token = $t;
        }
        $tokenCount = count($tokens);
        while (null !== $token && $nextId < $tokenCount && T_STRING !== $token) {
            $token = $tokens[-- $nextId];
            $tokenValue = $token;
            if (is_array($token)) {
                $t = $token[0];
                $tokenValue = $token[1];
                $token = $t;
            }
        }
        switch ($token) {
            case T_STRING:
                return array($tokenValue , $oldNextId + 1);
        }
        return array(null , $oldNextId + 1);
    }
    protected function parseTypeLine ($tokens, $nextId)
    {
        $oldNextId = $nextId;
        $token = $tokens[$nextId];
        $tokenValue = $token;
        if (is_array($token)) {
            $t = $token[0];
            $tokenValue = $token[1];
            $token = $t;
        }
        while (null !== $token && $nextId > 0 && T_STRING !== $token && ')' !== $tokenValue && '(' !== $tokenValue && ',' !== $tokenValue) {
            $token = $tokens[-- $nextId];
            $tokenValue = $token;
            if (is_array($token)) {
                $t = $token[0];
                $tokenValue = $token[1];
                $token = $t;
            }
        }
        if (T_STRING === $token) {
            return array($tokenValue , $oldNextId + 1);
        }
        return array(null , $oldNextId + 1);
    }
    public function parseDependencyLine (&$tokens, $nextId)
    {
        $token = $tokens[$nextId];
        $tokenCount = count($tokens);
        $dependency = '';
        while (null !== $token && $nextId < $tokenCount && ';' !== $token && (false === is_array($token) || (true === is_array($token) && T_CLOSE_TAG !== $token[0]))) {
            if (is_array($token)) {
                $token = $token[1];
            }
            $dependency .= $token;
            ++ $nextId;
            if ($nextId < $tokenCount) {
                $token = $tokens[$nextId];
            }
        }
        return array($dependency , $nextId - 1);
    }
}
